#include "stdafx.h"

//#include <pthread.h> /*NOTE: has to be the first header to use the thread safe version of the libraries!!*/
#include <stdio.h>
#include <stdarg.h>

#include "Debug.h"

#include <iostream>
#include <cstring>
#include <sstream>
#include <fstream>
#include <ctime>

#include <stdarg.h>

//#include <execinfo.h>
//#include <cxxabi.h>

using namespace std;



const int Debug::DEBUG = 0;
const int Debug::TESTING = 1;
const int Debug::VERBOSE = 2;
const int Debug::IMPORTANT = 3;
const int Debug::CRITICAL = 4;


int Debug::level = Debug::DEBUG;
int Debug::logLevel = Debug::DEBUG;
int Debug::highestLevel = Debug::CRITICAL;

string Debug::tag = "";
string Debug::applicationInfo = "[no Info]";

string Debug::logPath = ".";
string Debug::fullDebugLogPath = "./debug.log";
string Debug::fullErrorLogPath = "./error.log";
string Debug::debugLogName = "debug.log";
string Debug::errorLogName = "error.log";

ofstream* Debug::debugLog = new ofstream(Debug::fullDebugLogPath.c_str(), ios_base::app|ios_base::out);
ofstream* Debug::errorLog = new ofstream(Debug::fullErrorLogPath.c_str(), ios_base::app|ios_base::out);

//pthread_mutex_t Debug::debugLock = PTHREAD_MUTEX_INITIALIZER;
//pthread_mutex_t Debug::errorLock = PTHREAD_MUTEX_INITIALIZER;
//pthread_mutex_t Debug::outputLock = PTHREAD_MUTEX_INITIALIZER;

bool Debug::alreadyLogged = false;

bool Debug::doShowParams        = false;
bool Debug::doShowFunctionNames = true;
bool Debug::doShowFileNames     = false;
bool Debug::doShowLineNumbers   = true;

vector<string>* Debug::functionNameFilter = new vector<string>(1, "Debug::"); 



void Debug::message(string msg, int level){
	if (level >= Debug::level){
//		pthread_mutex_lock(&(Debug::outputLock));
		
		cout << Debug::tag;
		switch (level){
			case (Debug::DEBUG):
            case (Debug::TESTING):
				cout << "[DEBUG] ";
				break;
			case (Debug::VERBOSE):
				cout << "[NOTE] ";
				break;
			case (Debug::IMPORTANT):
				cout << "[NOTE] ";
				break;
			case (Debug::CRITICAL):
				cout << "[CRITICAL]  ";
				break;
		}

		
		cout << Debug::getCaller() << msg << endl;
//		pthread_mutex_unlock(&(Debug::outputLock));
	}
	
	Debug::log(msg, level);
}

void Debug::message(char* msg, int level, ...){
	if (level >= Debug::level || level >= Debug::logLevel){
		
		va_list list;
		char output[250];
		
		va_start(list, level);
		
		
		_vsnprintf(output, 250, msg, list);
		
		va_end(list);
		
		if (level >= Debug::level) {
//			pthread_mutex_lock(&(Debug::outputLock));
			cout << Debug::tag;
			switch (level){
				case (Debug::DEBUG):
				case (Debug::TESTING):
					cout << "[DEBUG] ";
					break;
				case (Debug::VERBOSE):
					cout << "[NOTE] ";
					break;
				case (Debug::IMPORTANT):
					cout << "[NOTE] ";
					break;
				case (Debug::CRITICAL):
					cout << "[CRITICAL]  ";
					break;
			}
			
			
			cout << Debug::getCaller() << output << endl;
//			pthread_mutex_unlock(&(Debug::outputLock));
		}
		if (level >= Debug::logLevel){
			Debug::log(output, level);	
		}
	}	
}

void Debug::log(string msg, int level){
	if (level >= Debug::logLevel){
		
		if (!Debug::alreadyLogged) {
			Debug::alreadyLogged = true;
			Debug::startLog(Debug::applicationInfo);
		}
		
		ostringstream output;
		
		time_t now = time(0);
		
		string timestamp(ctime(&now));
		
		output << "[" << timestamp.substr(0, timestamp.length() -2) << "] ";
		
		switch (level){
			case (Debug::DEBUG):
            case (Debug::TESTING):
				output << "[DEBUG] ";
				break;
			case (Debug::VERBOSE):
				output << "[NOTE] ";
				break;
			case (Debug::IMPORTANT):
				output << "[NOTE] ";
				break;
			case (Debug::CRITICAL):
				output << "[CRITICAL]  ";
				break;
		}

		output << Debug::getCaller()<< msg << endl;
		
		switch (level){
			case (Debug::DEBUG):
            case (Debug::TESTING):
			case (Debug::VERBOSE):
				/*if (debugLog->is_open()) {
					debugLog->close();
				}
				debugLog->open(fullDebugLogPath.c_str(), ios_base::app|ios_base::out);
				*debugLog << output.str().c_str();
				debugLog->close();*/
				writeToDebugLog(output.str());
				break;
			case (Debug::IMPORTANT):
			case (Debug::CRITICAL):
				/*if (errorLog->is_open()) {
					errorLog->close();
				}
				errorLog->open(fullErrorLogPath.c_str(), ios_base::app|ios_base::out);
				*errorLog << output.str().c_str();
				errorLog->close();*/
				writeToErrorLog(output.str());
				break;
		}
		
	}
}

void Debug::log(char* msg, int level, ...){
	if (level >= Debug::level){
		
		if (!Debug::alreadyLogged) {
			Debug::alreadyLogged = true;
			Debug::startLog(Debug::applicationInfo);
		}
		
		
		va_list list;
		char output_msg[250];
		ostringstream output;
		time_t now = time(0);
		
		va_start(list, level);
		
		
		_vsnprintf(output_msg, 250, msg, list);
		
		va_end(list);
		
		
		string timestamp(ctime(&now));
		
		output << "[" << timestamp.substr(0, timestamp.length() -2) << "] ";
		
		switch (level){
			case (Debug::DEBUG):
            case (Debug::TESTING):
				output << "[DEBUG] ";
				break;
			case (Debug::VERBOSE):
				output << "[NOTE] ";
				break;
			case (Debug::IMPORTANT):
				output << "[NOTE] ";
				break;
			case (Debug::CRITICAL):
				output << "[CRITICAL]  ";
				break;
		}

		output << Debug::getCaller()<< output_msg << endl;
		
		switch (level){
			case (Debug::DEBUG):
            case (Debug::TESTING):
			case (Debug::VERBOSE):
				/*if (debugLog->is_open()) {
					debugLog->close();
				}
				debugLog->open(fullDebugLogPath.c_str(), ios_base::app|ios_base::out);
				*debugLog << output.str().c_str();
				debugLog->close();*/
				writeToDebugLog(output.str());
				break;
			case (Debug::IMPORTANT):
			case (Debug::CRITICAL):
				/*if (errorLog->is_open()) {
					errorLog->close();
				}
				errorLog->open(fullErrorLogPath.c_str(), ios_base::app|ios_base::out);
				*errorLog << output.str().c_str();
				errorLog->close();*/
				writeToErrorLog(output.str());
				break;
		}
	}	
}


void Debug::hexdump(string type, unsigned char* data, int length, int width, int level){
    if (level >= Debug::level || level >= Debug::logLevel){ 	
    	
//    	pthread_mutex_lock(&(Debug::outputLock));
    	
    	ostringstream lineBuffer;
    	ostringstream out;
    	ostringstream logOut;
    	
    	time_t now = time(0);
    	string temp(ctime(&now));
        string timestamp(temp.substr(0, temp.length() -2));
    	
    	out << endl;
    	
    	string levelText;
    	
        out << endl;
        logOut << endl;
        //lineBuffer << Debug::tag;
        
        
        switch (level){
            case (Debug::DEBUG):
            case (Debug::TESTING):
                levelText = "[DEBUG] ";
                break;
            case (Debug::VERBOSE):
                levelText = "[NOTE] ";
                break;
            case (Debug::IMPORTANT):
                levelText = "[NOTE] ";
                break;
            case (Debug::CRITICAL):
                levelText = "[CRITICAL]  ";
                break;
        }

        lineBuffer << levelText << Debug::getCaller() << type << ": " << endl;
        
        out << Debug::tag << lineBuffer.str();
        logOut << "[" << timestamp << "] " << lineBuffer.str();
        lineBuffer.str("");
        
        
        lineBuffer << /*Debug::tag << */levelText << "length: " << dec << length;
        

        int line = 0;

        for (int i = 0; i < length; i++){
            if ((i % width) == 0){
            	
            	/*lineBuffer << " :";
            	lineBuffer.width(4);
            	lineBuffer.fill('0');
            	lineBuffer << dec << (line * width);*/
            	
				lineBuffer << endl;
				
				out << Debug::tag << lineBuffer.str();
		        logOut << "[" << timestamp << "] " << lineBuffer.str();
		        lineBuffer.str("");
             
              	lineBuffer << /*Debug::tag << */levelText;
               
            	lineBuffer.width(4);
            	lineBuffer.fill('0');
            	lineBuffer << dec << (line * width);
               
            	line++;

            	lineBuffer << ": ";
            }
            lineBuffer.width(2);
            lineBuffer.fill('0');
            lineBuffer << hex << (data[i] & 0xFF);
            lineBuffer << " ";
            if ((i % width) == ((width/2)-1) ) {
            	lineBuffer << " ";
            } 
        }

		lineBuffer << endl;
		
		out << Debug::tag << lineBuffer.str();
    	logOut << "[" << timestamp << "] " << lineBuffer.str();
    	
    	if (level >= Debug::level) {
    		cout << out.str();
    	}
    	if (level >= Debug::logLevel) {
    		
    		if (!Debug::alreadyLogged) {
				Debug::alreadyLogged = true;
				Debug::startLog(Debug::applicationInfo);
			}
    		
    		switch (level) {
			case (Debug::DEBUG):
            case (Debug::TESTING):
			case (Debug::VERBOSE):
				/*if (debugLog->is_open()) {
					debugLog->close();
				}
				debugLog->open(fullDebugLogPath.c_str(), ios_base::app|ios_base::out);
				*debugLog << logOut.str().c_str();
				debugLog->close();*/
				writeToDebugLog(logOut.str());
				break;
			case (Debug::IMPORTANT):
			case (Debug::CRITICAL):
				/*if (errorLog->is_open()) {
					errorLog->close();
				}
				errorLog->open(fullErrorLogPath.c_str(), ios_base::app|ios_base::out);
				*errorLog << logOut.str().c_str();
				errorLog->close();*/
				writeToErrorLog(logOut.str());
				break;
			}
    	}
//       pthread_mutex_unlock(&(Debug::outputLock)); 
    }
    
}

void Debug::setLevel(int level){
	if (level <= Debug::highestLevel) {
		Debug::level = level;
	} else {
		cout << Debug::tag << "[ERROR] Cannot set Debug Level: Invalid value" << endl;
	}
}

void Debug::setLogLevel(int level){
	if (level <= Debug::highestLevel) {
		Debug::logLevel = level;
	} else {
		cout << Debug::tag << "[ERROR] Cannot set Debug Level: Invalid value" << endl;
	}
}

void Debug::setQuiet(){
	Debug::level = Debug::highestLevel + 1;
}

void Debug::setLogQuiet(){
	Debug::logLevel = Debug::highestLevel + 1;
}

void Debug::setTag(string tag){
	ostringstream temp;
	
	if (tag.c_str()[0] != '[') {
		temp << "[";
	}
	
	temp << tag;
	
	if (tag.c_str()[tag.length()-1] != ']') {
		temp << "] ";
	}
	
	Debug::tag = temp.str();	
		
}

void Debug::setApplicationInfo(string info){
	Debug::applicationInfo = info;	
}

void Debug::setLogPath(string path) {
	
	/* this is a deep cut in the log file system, so gain access exclusively */
//	pthread_mutex_lock(&(Debug::debugLock));
//	pthread_mutex_lock(&(Debug::errorLock));
	
	
	ostringstream temp;
	ostringstream temp2;
	
	Debug::logPath = path;
	
	temp << path;
	temp2 << path;
	
	if (path[path.length()] != '/'){
		temp << "/";	
		temp2 << "/";	
	}
	
	temp << Debug::debugLogName;
	temp2 << Debug::errorLogName;
	
	Debug::fullDebugLogPath = temp.str();
	Debug::fullErrorLogPath = temp2.str();
	
	/* IMPORTANT: the mutexes MUST be unlocked, since they will be locked again
	 * in the called method, thus causing a deadlock if not unlocked beforehand */
//	pthread_mutex_unlock(&(Debug::debugLock));
//	pthread_mutex_unlock(&(Debug::errorLock));
	
	if (!Debug::alreadyLogged) {
		Debug::alreadyLogged = true;
		Debug::startLog(Debug::applicationInfo);
	}
}

void Debug::startLog(string info) {
	
	/* this is a deep cut in the log file system, so gain access exclusively */
//	pthread_mutex_lock(&(Debug::errorLock));
	
	
	if (debugLog->is_open()) {
		debugLog->close();
	}
	if (errorLog->is_open()) {
		errorLog->close();
	}
	
	ostringstream text;
	time_t now = time(0);
	string temp(ctime(&now));
    string timestamp(temp.substr(0, temp.length() -2));
	
	
	text << endl << endl;
	
	text << "***** Started logging: " << timestamp << " *****" << endl;
	text << info << endl << endl;
	
	debugLog->open(fullDebugLogPath.c_str(), ios_base::app|ios_base::out);
	*debugLog << text.str().c_str();
	debugLog->close();
	errorLog->open(fullErrorLogPath.c_str(), ios_base::app|ios_base::out);
	*errorLog << text.str().c_str();
	errorLog->close();
	
//	pthread_mutex_unlock(&(Debug::debugLock));
//	pthread_mutex_unlock(&(Debug::errorLock));
	
}

void Debug::writeToDebugLog(string text){
	
	/* serialize file access.
	 * This MAY impose a bottleneck, but since logging should be quite sparse,
	 * this should not be a problem.
	 * IF you need massive debug output, I suppose you are debugging and speed
	 * is not an issue anyways */
//	pthread_mutex_lock(&(Debug::debugLock));
	
	if (debugLog->is_open()) {
		debugLog->close();
	}
	
	debugLog->open(fullDebugLogPath.c_str(), ios_base::app|ios_base::out);
	*debugLog << text.c_str();
	debugLog->close();
	
//	pthread_mutex_unlock(&(Debug::debugLock));
}

void Debug::writeToErrorLog(string text){
	
	/* serialize file acces.
	 * This MAY impose a bottleneck, but if the program produces that many errors
	 * that speed is affected here, something is awfully wrong anyways */
//	pthread_mutex_lock(&(Debug::errorLock));
	
	if (errorLog->is_open()) {
		errorLog->close();
	}
	
	errorLog->open(fullErrorLogPath.c_str(), ios_base::app|ios_base::out);
	*debugLog << text.c_str();
	errorLog->close();
	
//	pthread_mutex_unlock(&(Debug::errorLock));
}


int Debug::getLevel(){
	return Debug::level;	
}

/**
 * return the fully qualified name of the function originally calling
 * any debugging function.
 * This can be toggled by calling Debug::showFunctionNames().
 * 
 * @return the fully qualified name of a function
 * 
 * @see Debug::showFunctionNames(bool)
 **/
string Debug::getCaller(){
	
	//cout << endl << " ---- " << endl;
	//string result;
	//ostringstream resultTmp;
	//
	//if (Debug::doShowFunctionNames){
	//	void*  adresses[10];
	//    size_t size;
	//    char** strings;
	//    char* demangle_temp;
	//    
	//    string demangled_string;
	//    
	//    string tmp;
	//
	//	int first;
	//	int last;
	//	int status;
	//	
	//	bool found = false;
	//	
	//	size = backtrace (adresses, 10);
	//	strings = backtrace_symbols (adresses, size);
	//	
	//	result = "main";
	//	
	//	//cout << "size: " << size << endl;
	//	
	//	for (size_t i = 0; i < size && !found; i++){
	//		tmp = strings[i];
	//		
	//		//cout << "strings[" << i << "] :" << strings[i] << endl; 
	//		
	//		first = tmp.rfind("(_Z");
	//		last = tmp.rfind("+0x");
	//		
	//		if (first != -1){
	//			tmp = tmp.substr(first+1, last-first-1);
	//
	//			demangle_temp = abi::__cxa_demangle(tmp.c_str(), 0, 0, &status);
	//	
	//			//cout << "demangle_temp: " << demangle_temp << endl;
	//	
	//			if (demangle_temp != 0){
	//				demangled_string = demangle_temp;
	//				found = true;
	//				
	//				/* see if any filter string is met */
	//				for (unsigned int j = 0; j < Debug::functionNameFilter->size(); j++){
	//					found &= ((demangled_string.rfind(Debug::functionNameFilter->at(j)) == string::npos));
	//				}
	//				if (found) {
	//					result = demangled_string;
	//					//cout << "result: " << demangled_string << "  XXXXXXX  " << endl;
	//					
	//				} else {
	//					//cout << "discarding string: " << demangled_string << "  XXXXXXX  " << endl;
	//				}
	//				
	//				//free(demangle_temp);
	//			}
	//		} else {
	//			//cout << "first == -1" << endl;	
	//		}
	//	}
	//	
	//	if (!(Debug::doShowParams)){
	//		
	//		result = result.substr(0, result.find_first_of('('));	
	//		
	//	}
	//
	//
	//
	//	//cout << " ---- " << endl;	
	//
	//	resultTmp << result << ": ";
	//	
	//	//free(adresses);
	//	free(strings);
	//	
	//	
	//} else {
	//	resultTmp << "";
	//	//cout << "nix function names";	
	//}
	//
	//return resultTmp.str();

	return "";
	
}

void Debug::printBacktrace(string note, int level, int stripLevels){
	
	
	/*if (level >= Debug::level){
		string levelText;
		switch (level){
	        case (Debug::DEBUG):
	        case (Debug::TESTING):
	            levelText = "[DEBUG] ";
	            break;
	        case (Debug::VERBOSE):
	            levelText = "[NOTE]  ";
	            break;
	        case (Debug::IMPORTANT):
	            levelText = "[IMPORTANT] ";
	            break;
	        case (Debug::CRITICAL):
	            levelText = "[CRITICAL]  ";
	            break;
	    }
    
    	cout << Debug::tag << levelText << note << endl;
    

		
	
	}
	Debug::log(note, level);
	
	Debug::printBacktrace(level, stripLevels);*/
}


void Debug::printBacktrace(int level, int stripLevels){
	
	
	//if (level >= Debug::level || level >= Debug::logLevel){ 
	//	ostringstream line;
	//	ostringstream logLine;
	//	ostringstream out;
	//	ostringstream logOut;
	//	
	//	void*  adresses[10];
	//    size_t size;
	//    char** strings;
	//    char* demangle_temp;
	//    
	//    string demangled_string;
	//    
	//    string tmp;
	//    string address;
	//
	//	int first;
	//	int last;
	//	int status;
	//	
	//	time_t now = time(0);
 //   	string temp(ctime(&now));
 //       string timestamp(temp.substr(0, temp.length() -2));
	//
	//	size = backtrace (adresses, 10);
	//	strings = backtrace_symbols (adresses, size);
	//	
	//	string levelText;
	//	
	//	
	//	switch (level){
 //           case (Debug::DEBUG):
 //           case (Debug::TESTING):
 //               levelText = "[DEBUG] ";
 //               break;
 //           case (Debug::VERBOSE):
 //               levelText = "[NOTE]  ";
 //               break;
 //           case (Debug::IMPORTANT):
 //               levelText = "[IMPORTANT] ";
 //               break;
 //           case (Debug::CRITICAL):
 //               levelText = "[CRITICAL]  ";
 //               break;
 //       }
	//	
	//	
	//	out << Debug::tag << levelText << "Backtrace:" << endl;
	//	logOut << "[" << timestamp << "] " << levelText << "Backtrace:" << endl;
	//	
	//	
	//	for (size_t i = stripLevels + 1; i < size; i++){
	//		
	//		
	//		line << Debug::tag << levelText << "    ";
	//		logLine << "[" << timestamp << "] " << levelText << "    ";
	//		
	//		tmp = strings[i];
	//		
	//		//out << "strings[" << i << "] :" << strings[i] << endl; 
	//		
	//		first = tmp.rfind("(_Z");
	//		last = tmp.rfind("+0x");
	//		
	//		int addr_first = tmp.rfind("[");
	//		int addr_last = tmp.rfind("]");
	//		
	//		
	//		if (first != -1){
	//			address = tmp.substr(addr_first, (addr_last - addr_first) + 1);
	//			
	//			tmp = tmp.substr(first+1, last-first-1);
	//			
	//
	//			demangle_temp = abi::__cxa_demangle(tmp.c_str(), 0, 0, &status);
	//	
	//			if (demangle_temp != 0){
	//				demangled_string = demangle_temp;
	//				
	//				/*if (!(Debug::doShowParams)){
	//					demangled_string = demangled_string.substr(0, demangled_string.find_first_of('('));	
	//				}*/
	//				
	//				line << demangled_string << " " << address << endl;
	//				logLine << demangled_string << " " << address << endl;
	//				
	//			} else {
	//				line << tmp << " " << address << endl;	
	//				logLine << tmp << " " << address << endl;	
	//			}
	//		} else {
	//			line << tmp << " " << address << endl;	
	//			logLine << tmp << " " << address << endl;	
	//		}		
	//	}
	//	
	//	out << line.str();
	//	logOut << logLine.str();
 //   	
 //   	if (level >= Debug::level) {
 //   		cout << out.str();
 //   	}
 //   	if (level >= Debug::logLevel) {
 //   		
 //   		if (!Debug::alreadyLogged) {
	//			Debug::alreadyLogged = true;
	//			Debug::startLog(Debug::applicationInfo);
	//		}
 //   		
 //   		switch (level) {
	//		case (Debug::DEBUG):
 //           case (Debug::TESTING):
	//		case (Debug::VERBOSE):
	//			writeToDebugLog(logOut.str());
	//			break;
	//		case (Debug::IMPORTANT):
	//		case (Debug::CRITICAL):
	//			writeToErrorLog(logOut.str());
	//			break;
	//		}
 //   	} 

	//}
}


void Debug::seperator(int level){

	Debug::seperator("----------------------------------------------------", level);
	
}

void Debug::seperator(std::string sep, int level){
	
	if (level >= Debug::level){
		cout << sep << endl;	
	}	
	if (level >= Debug::logLevel){
		
		ostringstream out;
		
		out << sep << endl;
		switch (level) {
			case (Debug::DEBUG):
			case (Debug::TESTING):
			case (Debug::VERBOSE):
				writeToDebugLog(out.str());
				break;
			case (Debug::IMPORTANT):
			case (Debug::CRITICAL):
				writeToErrorLog(out.str());
				break;
		}
	 }
	
}




